﻿// -----------------------------------------------------------------------
// <copyright file="VampiroServico.cs" company="Storytelling">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------
namespace Storytelling.Negocio
{
    using System.Collections.Generic;
    using System.Data.Objects;
    using System.Linq;
    using Storytelling.Entidades;
    using Storytelling.Entidades.VTR;
    using Storytelling.Modelos.VTR;
    using Storytelling.Negocio.Excecoes;

    public class VampiroServico : PersonagemExtensorServico<Vampiro, VampiroFormModel>
    {
        internal VampiroServico()
        {

        }

        protected override void PreOperacao(ObjectContext context, Vampiro entidade)
        {
            Vampiro vampiro = context.CreateObjectSet<Humano>().OfType<Vampiro>().Single(vam => vam.Id == entidade.Id);
            entidade.ClaId = vampiro.Cla.Id;

            if (vampiro.Coalizao != null)
            {
                entidade.CoalizaoId = vampiro.Coalizao.Id; 
            }

            entidade.Preferencial = vampiro.Preferencial;
            entidade.PenalidadeVontade = vampiro.PenalidadeVontade;

            entidade.Vontade -= entidade.PenalidadeVontade;

            if (entidade.VontadeTemporaria > entidade.Vontade)
            {
                entidade.VontadeTemporaria = entidade.Vontade;
            }
        }

        protected override void ValidarPersonagem(ObjectContext context, Vampiro entidade, bool inicial)
        {
            if (entidade.CoalizaoId.HasValue)
            {
                Coalizao coalizao = context.CreateObjectSet<Coalizao>().SingleOrDefault(coa => coa.Id == entidade.CoalizaoId.Value);
                if (!entidade.Vantagens.Any(huv => huv.VantagemId == coalizao.VantagemId && huv.Nivel >= 1))
                {
                    throw new StorytellingException(string.Format("Seu vampiro não pode escolher a coalizão '{0}', por que não tem a vantagem '{1}'",
                        coalizao.Nome, coalizao.Vantagem.Nome)); 
                }
            }

            if (inicial || context.CreateObjectSet<Humano>().OfType<Vampiro>()
                .Where(vam => vam.Id == entidade.Id).Any(vam => vam.Preferencial != entidade.Preferencial))
            {
                int limite = entidade.Fator < 6 ? 5 : entidade.Fator;
                bool ultrapassouLimite = false;

                switch (context.CreateObjectSet<Cla>().SingleOrDefault(cla => cla.Id == entidade.ClaId).Heranca.Value)
                {
                    case Heranca.Devas:
                        if (entidade.Preferencial)
                        {
                            if (!inicial)
                            {
                                entidade.Manipulacao--;
                            }
                            entidade.Destreza++;
                            ultrapassouLimite = entidade.Destreza >= limite;
                        }
                        else
                        {
                            if (!inicial)
                            {
                                entidade.Destreza--;
                            }
                            entidade.Manipulacao++;
                            ultrapassouLimite = entidade.Manipulacao >= limite;
                        }
                        break;
                    case Heranca.Gangrel:
                        if (entidade.Preferencial)
                        {
                            if (!inicial)
                            {
                                entidade.Vigor--;
                            }
                            entidade.Autocontrole++;
                            ultrapassouLimite = entidade.Autocontrole >= limite;
                        }
                        else
                        {
                            if (!inicial)
                            {
                                entidade.Autocontrole--;
                            }
                            entidade.Vigor++;
                            ultrapassouLimite = entidade.Vigor >= limite;
                        }
                        break;
                    case Heranca.Mekhet:
                        if (entidade.Preferencial)
                        {
                            if (!inicial)
                            {
                                entidade.Raciocinio--;
                            }
                            entidade.Inteligencia++;
                            ultrapassouLimite = entidade.Inteligencia >= limite;
                        }
                        else
                        {
                            if (!inicial)
                            {
                                entidade.Inteligencia--;
                            }
                            entidade.Raciocinio++;
                            ultrapassouLimite = entidade.Raciocinio >= limite;
                        }
                        break;
                    case Heranca.Nosferatu:
                        if (entidade.Preferencial)
                        {
                            if (!inicial)
                            {
                                entidade.Forca--;
                            }
                            entidade.Autocontrole++;
                            ultrapassouLimite = entidade.Autocontrole >= limite;
                        }
                        else
                        {
                            if (!inicial)
                            {
                                entidade.Autocontrole--;
                            }
                            entidade.Forca++;
                            ultrapassouLimite = entidade.Forca >= limite;
                        }
                        break;
                    case Heranca.Ventrue:
                        if (entidade.Preferencial)
                        {
                            if (!inicial)
                            {
                                entidade.Perseveranca--;
                            }
                            entidade.Presenca++;
                            ultrapassouLimite = entidade.Presenca >= limite;
                        }
                        else
                        {
                            if (!inicial)
                            {
                                entidade.Presenca--;
                            }
                            entidade.Perseveranca++;
                            ultrapassouLimite = entidade.Perseveranca >= limite;
                        }
                        break;
                    default:
                        break;
                }

                if (ultrapassouLimite)
                {
                    throw new StorytellingException("Seu vampiro não pode ter esse atributo preferencial, pois ele já se encontra no limite.");
                } 
            }
        }

        protected override void ValidarPersonagemInicial(ObjectContext context, Vampiro entidade)
        {
            int vantagens = context.CreateObjectSet<HumanoVantagem>().Where(huv => huv.HumanoId == entidade.Id)
                .Select(ent => ent.Nivel).ToArray().Aggregate((niv, nivNovo) =>
            {
                return niv + nivNovo;
            });

            for (int i = 1; i < entidade.Fator; i++)
            {
                vantagens += 3;
            }

            if (vantagens > 7 && entidade.Fator > 1)
            {
                throw new StorytellingException(string.Format("O seu vampiro não pode ter {0} ponto(s) de potência de sangue",
                    entidade.Fator));
            }

            int disciplinas = entidade.Disciplinas.Select(ent => ent.Nivel).Aggregate((niv, nivNovo) =>
            {
                return niv + nivNovo;
            });

            if (disciplinas != 3)
            {
                throw new StorytellingException(string.Format("Um vampiro inicial deve ter 3 pontos de disciplina. Valor: {0}",
                    disciplinas));
            }

            IEnumerable<long> disciplinasIds = context.CreateObjectSet<Cla>().SingleOrDefault(cla => cla.Id == entidade.ClaId)
                .Disciplinas.Select(cld => cld.DisciplinaId);

            if (entidade.CoalizaoId.HasValue)
            {
                disciplinasIds = disciplinasIds.Union(context.CreateObjectSet<Coalizao>().SingleOrDefault(coa => coa.Id == entidade.CoalizaoId.Value)
                    .Disciplinas.Select(cod => cod.DisciplinaId));
            }

            ObjectSet<Disciplina> objectSetDisciplina = context.CreateObjectSet<Disciplina>();

            foreach (var item in entidade.Disciplinas.Select(vpd => vpd.DisciplinaId))
            {
                Disciplina disciplina = objectSetDisciplina.SingleOrDefault(dis => dis.Id == item);
                if (disciplina == null || disciplina.Privado.Value && !disciplinasIds.Contains(item))
	            {
                    throw new StorytellingException(string.Format("Seu personagem não pode ter a disciplina '{0}', pois à mesma é privada de outro grupo", 
                        disciplina.Nome));
	            }
            }
            
            if (entidade.Disciplinas.Select(vpd => vpd.DisciplinaId).Count(id => !disciplinasIds.Contains(id)) > 1)
            {
                throw new StorytellingException("Apenas uma disciplina extra-clã pode ser escolhida");
            }
            else if (entidade.Disciplinas.Any(vpd => !disciplinasIds.Contains(vpd.DisciplinaId) && vpd.Nivel > 1))
            {
                throw new StorytellingException("Disciplina extra-clã não pode ter o nível maior que 1");
            }
        }

        protected override void Evoluir(ObjectContext context, Vampiro entidade, TipoCampo tipoCampo, long id, int incremento)
        {
            switch (tipoCampo)
            {
                case TipoCampo.Extensor:
                    if (id == 0)
                    {
                        for (int i = 0; i < incremento; i++)
                        {
                            entidade.Fator++;
                            entidade.Experiencia -= entidade.Fator * 8;
                        } 
                    }
                    else
                    {
                        for (int i = 0; i < incremento; i++)
                        {
                            if (entidade.PenalidadeVontade > 0)
                            {
                                entidade.PenalidadeVontade--;
                                entidade.Experiencia -= 8; 
                            }
                        }                         
                    }
                    break;
                case TipoCampo.Devocao:
                    Devocao devocao = context.CreateObjectSet<Devocao>().SingleOrDefault(ent => ent.Id == id);

                    if (!devocao.PreRequisitos.All(ent => {
                        VampiroDisciplina vd = entidade.Disciplinas.SingleOrDefault(vad => vad.DisciplinaId == ent.DisciplinaId);
                        return (vd != null ? vd.Nivel : 0) >= ent.Nivel;
                    }))
                    {
                        throw new StorytellingException("Seu vampiro não atende os pré-requisitos das devoções atribuídas.");
                    }

                    entidade.Experiencia -= devocao.Custo;
                    VampiroDevocao vampiroDevocao = new VampiroDevocao();
                    vampiroDevocao.VampiroId = entidade.Id;
                    vampiroDevocao.DevocaoId = id;
                    context.AddObject("VampiroDevocao", vampiroDevocao);
                    break;
                case TipoCampo.Disciplina:
                    ObjectSet<Disciplina> objectSetDisciplina = context.CreateObjectSet<Disciplina>();
                    Disciplina disciplina = objectSetDisciplina.SingleOrDefault(dis => dis.Id == id);

                    if (disciplina == null || (disciplina.Privado.Value && (context.CreateObjectSet<ClaDisciplina>()
                        .SingleOrDefault(cld => cld.ClaId == entidade.ClaId && cld.DisciplinaId == id) == null
                        && (!entidade.CoalizaoId.HasValue || (entidade.CoalizaoId.HasValue && context.CreateObjectSet<CoalizaoDisciplina>()
                        .SingleOrDefault(cod => cod.CoalizaoId == entidade.CoalizaoId.Value && cod.DisciplinaId == id) == null)))))
	                {
                        throw new StorytellingException(string.Format("Seu personagem não pode ter a disciplina '{0}', pois à mesma é privada de outro grupo",
                            disciplina.Nome));
	                }

                    int extra = 0;

                    if (disciplina.AgrupamentoId.HasValue)
                    {
                        foreach (var item in from vd in entidade.Disciplinas
                            from dis in objectSetDisciplina 
                            where vd.DisciplinaId == dis.Id &&
                            vd.DisciplinaId != disciplina.Id &&
                            dis.AgrupamentoId == disciplina.AgrupamentoId.Value
                            select vd.Nivel)
	                    {
		                     extra += item;
	                    }
                    }

                    bool isFamiliar = entidade.Cla.Disciplinas.Any(ent => ent.DisciplinaId == id);

                    VampiroDisciplina vampiroDisciplina = entidade.Disciplinas.SingleOrDefault(ent => ent.DisciplinaId == id);

                    if (vampiroDisciplina == null)
                    {
                        vampiroDisciplina = new VampiroDisciplina();
                        vampiroDisciplina.VampiroId = entidade.Id;
                        vampiroDisciplina.DisciplinaId = id;
                    }

                    for (int i = 0; i < incremento; i++)
                    {
                        vampiroDisciplina.Nivel++;
                        entidade.Experiencia -= (vampiroDisciplina.Nivel + extra) * (isFamiliar ? 5 : 7);
                    }

                    if (vampiroDisciplina.Id == 0)
                    {
                        context.AddObject("VampiroDisciplina", vampiroDisciplina);
                    }
                    else
                    {
                        context.ApplyCurrentValues("VampiroDisciplina", vampiroDisciplina);
                    }
                    break;
                case TipoCampo.Ritual:
                    Ritual ritual = context.CreateObjectSet<Ritual>().SingleOrDefault(ent => ent.Id == id);

                    if (!entidade.Disciplinas.Any(vpd => vpd.DisciplinaId == ritual.DisciplinaId && vpd.Nivel >= ritual.Nivel))
                    {
                        throw new StorytellingException(string.Format("Seu vampiro não tem o nível mínimo permitido na disciplina '{0}' para comprar o ritual '{1}'.",
                                ritual.Disciplina.Nome, ritual.Nome));
                    }

                    if (entidade.Rituais.Any(vpr => vpr.Ritual.DisciplinaId == ritual.DisciplinaId && vpr.Ritual.Nivel == ritual.Nivel))
                    {
                        entidade.Experiencia -= ritual.Nivel.Value * 2; 
                    }

                    VampiroRitual vampiroRitual = new VampiroRitual();
                    vampiroRitual.VampiroId = entidade.Id;
                    vampiroRitual.RitualId = id;
                    context.AddObject("VampiroRitual", vampiroRitual);
                    break;
            }
        }

        protected override void GastarExperiencia(ObjectContext context, Vampiro entidade)
        {
            entidade.Vontade -= entidade.PenalidadeVontade;

            if (entidade.VontadeTemporaria > entidade.Vontade)
            {
                entidade.VontadeTemporaria = entidade.Vontade;
            }
        }

        protected override bool PermitirVantagem(Vantagem vantagem)
        {
            return vantagem.UsadoPorVampiro.Value;
        }

        protected override bool PermitirVantagemDesconto(ObjectContext context, Vampiro entidade, Vantagem vantagem)
        {
            return entidade.CoalizaoId.HasValue && context.CreateObjectSet<CoalizaoVantagem>()
                .Any(cov => cov.CoalizaoId == entidade.CoalizaoId.Value && cov.VantagemId == vantagem.Id);
        }

        protected override void PreValoresCorrente(Vampiro entidade, Vampiro entidadeModel)
        {
            entidade.ClaId = entidadeModel.ClaId;
            entidade.CoalizaoId = entidadeModel.CoalizaoId;
            entidade.Fator = entidadeModel.Fator;
            entidade.Preferencial = entidadeModel.Preferencial;
            entidade.PenalidadeVontade = entidadeModel.PenalidadeVontade;

            entidade.Vontade -= entidade.PenalidadeVontade;

            if (entidade.VontadeTemporaria > entidade.Vontade)
            {
                entidade.VontadeTemporaria = entidade.Vontade;
            }
        }

        protected override void PosValoresCorrente(ObjectContext context, Vampiro entidade)
        {
            foreach (var item in entidade.Disciplinas)
            {
                if (item.Id == 0)
                {
                    context.AddObject("VampiroDisciplina", item);
                }
                else
                {
                    context.ApplyOriginalValues("VampiroDisciplina", context.GetObjectByKey(context.CreateEntityKey("VampiroDisciplina", item)));
                    context.ApplyCurrentValues("VampiroDisciplina", item);
                }
            }

            foreach (var item in entidade.Devocoes)
            {
                if (item.Id == 0)
                {
                    context.AddObject("VampiroDevocao", item);
                }
            }

            foreach (var item in entidade.Rituais)
            {
                if (item.Id == 0)
                {
                    context.AddObject("VampiroRitual", item);
                }
            }
        }

        protected override void CarregarPropriedades(ObjectContext context, Vampiro entidade)
        {
            context.LoadProperty<Vampiro>(entidade, vam => vam.Cla);
            context.LoadProperty<Vampiro>(entidade, vam => vam.Coalizao);
            context.LoadProperty<Vampiro>(entidade, vam => vam.Devocoes);
            context.LoadProperty<Vampiro>(entidade, vam => vam.Disciplinas);
            context.LoadProperty<Vampiro>(entidade, vam => vam.Rituais);

            foreach (var item in entidade.Disciplinas)
            {
                context.LoadProperty<VampiroDisciplina>(item, ent => ent.Disciplina);
            }

            foreach (var item in entidade.Devocoes)
            {
                context.LoadProperty<VampiroDevocao>(item, ent => ent.Devocao);
            }

            foreach (var item in entidade.Rituais)
            {
                context.LoadProperty<VampiroRitual>(item, ent => ent.Ritual);
            }
        }

        protected override void GerenciarEnergia(ObjectContext context,  Vampiro entidade, bool sinal, int valor, IList<string> status)
        {
            int limite = 10;

            switch (entidade.Fator)
            {
                case 2:
                    limite = 11;
                    break;
                case 3:
                    limite = 12;
                    break;
                case 4:
                    limite = 13;
                    break;
                case 5:
                    limite = 14;
                    break;
                case 6:
                    limite = 15;
                    break;
                case 7:
                    limite = 20;
                    break;
                case 8:
                    limite = 30;
                    break;
                case 9:
                    limite = 50;
                    break;
                case 10:
                    limite = 100;
                    break;
            }

            if (sinal)
            {
                entidade.Energia += valor;

                if (entidade.Energia > limite)
                {
                    entidade.Energia = limite;
                }
            }
            else
            {
                entidade.Energia -= valor;

                if (entidade.Energia < 0)
                {
                    entidade.Energia = 0;
                }
            }

            status.Add(string.Format("Pontos de sangue = {0} / {1}", entidade.Energia, limite));
        }

        protected override void Validar(Vampiro entidade)
        {
            if (entidade.Fator > 10)
            {
                throw new StorytellingException("Um vampiro não deve ter mais de 10 pontos em potência de sangue");
            }

            int limite = entidade.Fator < 6 ? 5 : entidade.Fator;

            if (entidade.Disciplinas.Any(vad => vad.Nivel > limite))
            {
                throw new StorytellingException(string.Format("Um vampiro não deve ter mais de {0} pontos, pois sua potência de sangue é {1}", limite, entidade.Fator));
            }
        }

        protected override void ValidarUsoVantagens(ICollection<HumanoVantagem> vantagens)
        {
            if (!vantagens.All(hvn => hvn.Vantagem.UsadoPorVampiro.Value))
            {
                throw new StorytellingException("Seu personagem tem vantagens que não podem ser usadas por um vampiro.");
            } 
        }

        public KeyValuePair<int, KeyValuePair<long, bool>> ComprarRitual(long id, long idRitual)
        {
            using (var context = Context)
            {
                Ritual ritual = context.CreateObjectSet<Ritual>().SingleOrDefault(rit => rit.Id == idRitual);

                int nivel = 0;
                bool isGratis = false;

                if ((from vpd in context.CreateObjectSet<VampiroDisciplina>()
                     where vpd.VampiroId == id && vpd.DisciplinaId == ritual.DisciplinaId &&
                     vpd.Nivel >= ritual.Nivel select vpd).Any())
                {
                    nivel = ritual.Nivel.Value;
                    isGratis = !(from vam in context.CreateObjectSet<Humano>().OfType<Vampiro>()
                                 from vpr in vam.Rituais where vam.Id == id && 
                                 vpr.Ritual.DisciplinaId == ritual.DisciplinaId && 
                                 vpr.Ritual.Nivel == ritual.Nivel
                                 select vpr).Any();
                }

                return new KeyValuePair<int, KeyValuePair<long, bool>>(nivel, new KeyValuePair<long, bool>(ritual.DisciplinaId.Value, isGratis));
            }
        }
    }
}
